package com.wu.demo.controller;

import com.alibaba.fastjson.JSONObject;
import com.wu.demo.common.annotation.IgnoreAuth;
import com.wu.demo.common.domain.AjaxResult;
import io.swagger.annotations.Api;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Auther: wu
 * @Date: 2023/12/6
 * @Description: com.wu.demo.controller
 */
@Api(tags = "抢红包测试")
@RestController
@RequestMapping("redPacket")
@IgnoreAuth
public class RedPacketController {
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private RedissonClient redissonClient;

    @GetMapping("/test")
    public AjaxResult test(){
        System.out.println(redisTemplate.opsForValue().get("abc"));
        redisTemplate.opsForValue().set("abcde",null,5,TimeUnit.MINUTES);
        System.out.println(redisTemplate.opsForValue().get("abcde"));
        System.out.println(redisTemplate.hasKey("abcde"));
        return AjaxResult.success();
    }

    /**
     * 发红包
     * @param totalMoney 红包金额
     * @param redNum 红包数量
     * @return
     */
    @GetMapping("/send")
    public AjaxResult sendRedPacket(int totalMoney, int redNum){
        String redPacketId = UUID.randomUUID().toString();//红包ID
        String redKey = "redInfo:" + redPacketId;
        // 实时拆分
        //redisTemplate.opsForValue().set("rp:balance:"+redPacketId,totalMoney,24, TimeUnit.HOURS);
        //redisTemplate.opsForValue().set("rp:count:"+redPacketId,redNum,24, TimeUnit.HOURS);
        //预拆分
        List<Double> redPacketList = divideRedPacket( totalMoney, redNum);
        //预拆分，把拆分红包信息存入redis中
        redisTemplate.opsForList().leftPushAll(redKey,redPacketList);
        //设置过期时间，过期返还
        redisTemplate.expire(redKey,24, TimeUnit.HOURS);
        System.out.println( JSONObject.toJSONString(redPacketList));
        return AjaxResult.success(redPacketId);
    }

    /**
     * 预拆分红包 抢红包
     * @param redPacketId
     * @param userId
     * @return
     */
    @GetMapping("/get")
    public AjaxResult getRedPacket(String redPacketId,String userId){
        //加锁，避免多线程下
        RLock rLock = redissonClient.getLock("redPacket:"+redPacketId);
        Double redAmount = null;
        try {//rLock.lock();
            rLock.tryLock(5,TimeUnit.SECONDS);
            //通过hashMap.isExistsKey() 判断领取红包的记录，防止重复领取
            Boolean isReceived = redisTemplate.opsForHash().hasKey("redUser:"+redPacketId,userId);
            if(isReceived){
                System.out.println(userId + "已经抢过红包"+redPacketId);
                return AjaxResult.success(userId + "已经抢过红包"+redPacketId);
            }
            //抢红包，根据红包id从redis中获取红包列表
            redAmount = (Double) redisTemplate.opsForList().rightPop("redInfo:" + redPacketId);
            if(redAmount == null){
                System.out.println(userId + "红包已抢完"+redPacketId);
                return AjaxResult.success(userId + "红包已抢完"+redPacketId);
            }
            //操作红包记录hash表，记录红包领取记录
            redisTemplate.opsForHash().put("redUser:"+redPacketId,userId,redAmount);
            System.out.println(userId + "抢到了"+redAmount+"元");

        }catch (Exception e){
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }
        return AjaxResult.success(userId + "抢到了"+redAmount+"元");
    }

    public static List<Double> divideRedPacket(int totalMoney,int num){
        //剩余未分配金额
        double restAmount = totalMoney;
        //剩余红包数量
        int restNum = num;
        Random random = new Random();
        List<Double> redList = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            if(i == num - 1){//最后一个红包
                double redAmount = restAmount;
                redList.add(redAmount);
            }else {
                //当前可抢红包的最大值，利用2倍均值法拆分
                double maxAmount = restAmount * 2 / restNum;
                double redAmount = random.nextDouble() * maxAmount;//随机[0,1)之间的小数 * 最大金额
                //红包最小金额为0.01，如果随机redAmount小于0.01则取0.01,保留两位小数
                redAmount = Double.valueOf(String.format("%.2f",Math.max(0.01,redAmount)));
                redList.add(redAmount);
                //剩余可拆分红包金额
                restAmount = new BigDecimal(restAmount+"").subtract(new BigDecimal(redAmount+"")).doubleValue();//剩余金额
                restNum --;//剩余人数
            }
        }
        redList.stream().forEach(a-> System.out.print(a+","));
        return redList;
    }


    /**
     * 实时拆红包
     * lua脚本抢红包，发红包是只存储红包金额和数据量，无需预拆分
     * @param redPacketId
     * @param userId
     * @return
     */
    @RequestMapping("/luaGet")
    public AjaxResult receiveRedPacketCAS(String redPacketId,String userId) {
        // 通过 hexists 命令判断红包领取记录防重 Hash 表中用户是否领取过红包 ，若用户未领取过红包，流程继续:
        // hashmap.isExistsKey()
        Boolean isReceived = redisTemplate.opsForHash().hasKey( "RED_PACKET" + redPacketId, userId);
        if (!isReceived) {
            //Lua脚本在redis中是单线程 串行执行的
            String luaScript =
                    "local function divideRedPacket(key_amount, key_count, key_hash, userId) " + System.lineSeparator() +
                            "   -- 获取红包金额和总个数" + System.lineSeparator() +
                            "   local total_amount = tonumber(redis.call('GET', key_amount)) " + System.lineSeparator() +
                            "   local total_count = tonumber(redis.call('GET', key_count)) " + System.lineSeparator() +
                            "   -- 确保红包还有剩余" + System.lineSeparator() +
                            "   if (total_count <= 0)  then" + System.lineSeparator() +
                            "       return '红包已抢完' " + System.lineSeparator() +
                            "   end " + System.lineSeparator() +
                            "   -- 二倍均值法计算红包金额 " + System.lineSeparator() +
                            "   local max_amount = total_amount / total_count * 2 "+ System.lineSeparator() +
                            "   local random_amount = math.random() * max_amount " +System.lineSeparator() +
                            "   local final_amount = string.format('%.2f', math.max(0.01, random_amount)) " +System.lineSeparator() +
                            "   -- 最小金额为0.01元"+System.lineSeparator() +
                            "   -- 更新红包金额和总个数" +System.lineSeparator() +
                            //DECRBYFLOAT 当前redis版本不支持,使用 INCRBYFLOAT ，-负数
                            "   redis.call('INCRBYFLOAT', key_amount, -final_amount) "+System.lineSeparator() +
                            "   redis.call('DECR', key_count)  " +System.lineSeparator() +
                            "   -- 将拆出米的红包企额和userId放入Redis的Hash结构中 "+System.lineSeparator() +
                            "   redis.call('HSET', key_hash, userId, final_amount) " +System.lineSeparator() +
                            "   return '用户'.. userId ..'抢到红包金额'.. final_amount " +System.lineSeparator() +
                            "end  "+System.lineSeparator() +
                            "-- 调用拆红包雨数，传入红包金额和总个数的key,以及用户的userId " +System.lineSeparator() +
                            "return divideRedPacket(KEYS[1],KEYS[2],KEYS[3],ARGV[1]) ";

            // 创建RedisScript对象
            RedisScript<String> script = new DefaultRedisScript<>(luaScript, String.class);
            String res = (String) redisTemplate.execute(script, Arrays.asList("rp:balance:"+redPacketId,"rp:count:"+redPacketId, "RED_PACKET"+ redPacketId), userId);
            return AjaxResult.success(res) ;
        }
        return AjaxResult.success(userId + "已经抢过红包了") ;
    }


    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            divideRedPacket(100,6);
            System.out.println();
        }



    }
}
